from scipy.interpolate import make_interp_spline, CubicSpline

stock_file_name = 'input/stock_list.csv'
stock_list = pd.read_csv(stock_file_name)[['code','name']]
stock_list = stock_list.reset_index(drop=True)
'''统计二板个股跌幅比例，查看当前环境下，低吸策略是否适用'''
def show_10cm_zt2_low_precent(df):
    df = filter_10cm(df)
    df = df.sort_values(by=['code', 'date'])
    df['prev_close'] = df.groupby('code')['close'].shift(1)
    df['open_pct'] = (df['open'] - df['prev_close']) / df['prev_close'] * 100
    df['low_pct'] = (df['low'] - df['prev_close']) / df['prev_close'] * 100
    df = df[df['is_zt'] & (df['zt_num'] ==2) & (df['pre_r1'] > 9) ]
    df = df.dropna(subset=['prev_close']).reset_index(drop=True)
    df = df.round(2).sort_values(by=['date'])
    return df



'''统计二板个股跌幅比例，查看当前环境下，低吸策略是否适用'''
def show_10cm_zt3_low_precent(df):
    df = filter_10cm(df)
    df = df.sort_values(by=['code', 'date'])
    df['prev_close'] = df.groupby('code')['close'].shift(1)
    df['open_pct'] = (df['open'] - df['prev_close']) / df['prev_close'] * 100
    df['low_pct'] = (df['low'] - df['prev_close']) / df['prev_close'] * 100
    df = df[df['is_zt'] & (df['zt_num'] > 2) & (df['pre_r1'] > 9) ]
    df = df.dropna(subset=['prev_close']).reset_index(drop=True)
    df = df.round(2).sort_values(by=['date'])
    df = pd.merge(stock_list,df,on='code')
    df = df[['date','code','name','open','low','close','high','open_pct','low_pct','zt_num']].reset_index(drop=True)
    df = get_previous_days_data(df,0)    
    df = df.sort_values(by=[ 'zt_num'],ascending=False)
    return df

def show_day_gt_zt3(df,index_day = 1):
    df = show_10cm_zt3_low_precent(df)
    df = get_nth_recent_data(df)
    df = df.sort_values(by=['zt_num'],ascending=False)
    return df

import pandas as pd

def get_nth_recent_data(df, date_col='date', n=1, date_format=None):
    """
    获取每个分组倒数第n天的数据
    
    参数:
    df: 输入的DataFrame
    date_col: 日期列名 (默认'date')
    n: 取倒数第n天的数据 (默认1，即最新一天)
    date_format: 可选日期格式 (如'%Y-%m-%d')
    
    返回:
    包含倒数第n天数据的DataFrame
    """
    # 创建副本避免修改原数据
    df = df.copy()
    
    # 转换为datetime格式（自动识别常见格式）
    df[date_col] = pd.to_datetime(df[date_col], errors='coerce', format=date_format)
    
    # 删除无效日期
    df = df.dropna(subset=[date_col])
    
    # 按日期降序排序
    df = df.sort_values(date_col, ascending=False)
    
    # 获取倒数第n个日期
    target_date = df[date_col].unique()[n-1] if n <= len(df[date_col].unique()) else None
    
    # 返回目标日期的数据
    return df[df[date_col] == target_date] if target_date is not None else pd.DataFrame()





'''统计二板个股跌幅比例，查看当前环境下，低吸策略是否适用'''
def show_10cm_zt2_low_precent(df):
    df = filter_10cm(df)
    df = df.sort_values(by=['code', 'date'])
    df['prev_close'] = df.groupby('code')['close'].shift(1)
    df['open_pct'] = (df['open'] - df['prev_close']) / df['prev_close'] * 100
    df['low_pct'] = (df['low'] - df['prev_close']) / df['prev_close'] * 100
    df = df[df['is_zt'] & (df['zt_num'] ==2) & (df['pre_r1'] > 9) ]
    df = df.dropna(subset=['prev_close']).reset_index(drop=True)
    df = df.round(2).sort_values(by=['date'])
    df = pd.merge(stock_list,df,on='code')
    df = df.sort_values(by=[ 'date'])
    df = df[['date','code','name','open','low','close','high','open_pct','low_pct','zt_num']]
    return df

def show_10cm_zt1_low_precent(df):
    df = filter_10cm(df)
    df = df.sort_values(by=['code', 'date'])
    df['prev_close'] = df.groupby('code')['close'].shift(1)
    df['open_pct'] = (df['open'] - df['prev_close']) / df['prev_close'] * 100
    df['low_pct'] = (df['low'] - df['prev_close']) / df['prev_close'] * 100
    df['low_pct'] = df['low_pct'].clip(lower=-10)

    df = df[df['is_zt'] & (df['zt_num'] ==1) ]
    df = df.dropna(subset=['prev_close']).reset_index(drop=True)
    df = df.round(2).sort_values(by=['date'])
    return df



def show_10cm_zt_precent(df):
    df = filter_10cm(df)
    df = df.sort_values(by=['code', 'date'])
    df['prev_close'] = df.groupby('code')['close'].shift(1)
    df['open_pct'] = (df['open'] - df['prev_close']) / df['prev_close'] * 100
    df['low_pct'] = (df['low'] - df['prev_close']) / df['prev_close'] * 100
    df['low_pct'] = df['low_pct'].clip(lower=-10)

    df = df[df['is_zt'] ]
    df = df.dropna(subset=['prev_close']).reset_index(drop=True)
    df = df.round(2).sort_values(by=['date'])

    return df

"""
    获取最近一天的数据（默认取第0索引即最近一天）
    
    参数：
    day_df : 包含多日数据的DataFrame，必须包含日期列
    index : 整数索引，0表示最近一天，1表示前一天，以此类推
    
    返回：
    指定索引对应日期的数据子集
"""
def get_recent_day_data(day_df, index=0):
    
    # 1. 检查日期列是否存在
    if 'date' not in day_df.columns:
        raise ValueError("DataFrame必须包含'date'列")
    
    # 2. 按日期降序排序（确保最近日期在最前面）
    sorted_df = day_df.sort_values(by='date', ascending=False)
    
    # 3. 获取唯一日期并排序
    unique_dates = sorted_df['date'].unique()
    
    # 4. 检查索引是否有效
    if index >= len(unique_dates):
        raise IndexError(f"索引超出范围。可用日期数: {len(unique_dates)}，请求索引: {index}")
    
    # 5. 获取目标日期
    target_date = unique_dates[index]
    
    # 6. 返回目标日期的数据
    return sorted_df[sorted_df['date'] == target_date]



import pandas as pd
import matplotlib.pyplot as plt

def create_daily_low_pct_summary(df):
    """
    创建包含每日最小low_pct和负值数量的新DataFrame
    
    参数:
    df - 包含date和low_pct列的原始DataFrame
    
    返回:
    new_df - 包含以下列的新DataFrame:
        date: 日期
        min_low_pct: 每日最小low_pct值
        neg_count: 每日low_pct小于0的数量
    """
    # 确保日期格式正确并按日期排序
    df['date'] = pd.to_datetime(df['date'])
    df = df.sort_values('date')
    
    # 按日期分组并计算最小low_pct和负值数量
    daily_summary = df.groupby('date').agg(
        min_low_pct=('low_pct', 'min'),
        neg_count=('low_pct', lambda x: (x < 0).sum())
    ).reset_index()
    
    # 重命名列并返回结果
    return daily_summary.rename(columns={'date': 'date', 'min_low_pct': 'min_low_pct', 'neg_count': 'neg_count'})



import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.ticker import MaxNLocator

def plot_min_low_pct_and_neg_count(new_df):
    """
    创建简洁的双轴图表：
    - 左轴：min_low_pct折线图（蓝色）
    - 右轴：neg_count柱状图（橙色）
    
    参数:
    new_df - 包含date, min_low_pct, neg_count列的DataFrame
    """
    # 确保日期列是datetime格式并排序
    new_df['date'] = pd.to_datetime(new_df['date'])
    new_df = new_df.sort_values('date')
    
    # 创建图形和左轴（折线图）
    fig, ax1 = plt.subplots(figsize=(12, 6))
    
    # 绘制min_low_pct折线图
    ax1.plot(new_df['date'], new_df['min_low_pct'], 
             color='#CC0000',  # 标准蓝色
             linewidth=2.0,
             marker='o',
             markersize=5,
             label='Min low_pct')
    
    # 设置折线图标签和颜色
    ax1.set_xlabel('Date')
    ax1.set_ylabel('Min low_pct', color='#1f77b4')
    ax1.tick_params(axis='y', labelcolor='#1f77b4')
    
    # 添加零线参考
    ax1.axhline(0, color='gray', linestyle='-', linewidth=1, alpha=1)
    
    # 设置标题
    plt.title('Min low_pct and Negative Count', pad=12)
    
    # 添加图例
    lines, labels = ax1.get_legend_handles_labels()
    
    # 优化日期格式
    plt.gcf().autofmt_xdate()
    
    # 显示图表
    plt.tight_layout()
    plt.show()





import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import matplotlib.dates as mdates

# 设置中文字体和大字体适合手机查看
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体
plt.rcParams['axes.unicode_minus'] = False    # 正常显示负号
plt.rcParams['font.size'] = 12               # 基础字体大小

def plot_streak_analysis_mobile(data):
    """
    移动端优化的连板分析图表
    data: DataFrame包含以下列
        date: 日期
        code: 股票代码
        name: 股票名称
        open: 开盘价
        low: 最低价
        close: 收盘价
        high: 最高价
        open_pct: 开盘涨跌幅
        low_pct: 盘中最大跌幅
        zt_num: 连板天数
    """
    # 复制数据避免修改原始数据
    df = data.copy()
    
    # 确保日期格式正确
    df['date'] = pd.to_datetime(df['date'])
    
    # 修复 ValueError: 'date' is both an index level and a column label
    if 'date' in df.index.names:
        df = df.reset_index()
    
    # 1. 计算每日各连板级别的数量
    # 创建连板分组列
    df['streak_group'] = df['zt_num'].apply(lambda x: 
        '首板' if x == 1 else
        '二板' if x == 2 else
        '三板' if x == 3 else
        '四板' if x == 4 else
        '五板+' if x >= 5 else '其他'
    )
    
    # 2. 计算每日统计
    daily_counts = df.groupby(['date', 'streak_group']).size().unstack(fill_value=0)
    daily_counts = daily_counts[['首板', '二板', '三板', '四板', '五板+']]  # 确保顺序
    
    # 3. 计算最高连板
    max_streak = df.groupby('date')['zt_num'].max().rename('最高板')
    
    # 4. 计算各连板级别的平均最大跌幅
    drawdown_avg = df.groupby(['date', 'streak_group'])['low_pct'].mean().unstack(fill_value=0)
    drawdown_avg = drawdown_avg[['首板', '二板', '三板', '四板', '五板+']]  # 确保顺序
    
    # 5. 合并结果
    result = daily_counts.join(max_streak).join(drawdown_avg, rsuffix='_跌幅')
    
    # 只显示最近20个交易日数据
    result = result.sort_index().tail(20)
    
    # 创建两个图表
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 14), dpi=100)
    
    # --- 图表1: 连板数量分析 ---
    # 设置颜色
    colors = ['#FF9999', '#FFCC99', '#FFD700', '#66B3FF', '#99FF99']
    labels = ['首板', '二板', '三板', '四板', '五板+']
    
    # 底部堆叠起始点
    bottom = np.zeros(len(result))
    
    # 绘制堆叠柱状图
    for i, col in enumerate(labels):
        ax1.bar(result.index, result[col], 
                bottom=bottom, color=colors[i], 
                width=0.8, label=col)
        bottom += result[col]
    
    # 添加最高连板数点
    ax1.scatter(result.index, result['最高板'], 
               s=80, color='red', zorder=5, label='最高板')
    
    # 添加最高板数值标签
    for date, row in result.iterrows():
        ax1.text(date, row['最高板'] + 0.5, 
                f"{int(row['最高板'])}板", 
                ha='center', fontsize=10, color='red')
    
    # 设置图表1样式
    ax1.set_title('连板数量分析', fontsize=16, pad=15)
    ax1.set_ylabel('数量', fontsize=14)
    ax1.grid(axis='y', linestyle='--', alpha=0.7)
    ax1.legend(loc='upper left', bbox_to_anchor=(1, 1))
    ax1.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
    
    # --- 图表2: 连板股最大跌幅分析 ---
    # 绘制各连板级别最大跌幅折线
    for i, group in enumerate(labels):
        col = f'{group}_跌幅'
        ax2.plot(result.index, result[col], 
                 'o-', color=colors[i], label=f'{group}', 
                 linewidth=2, markersize=6)
    
    # 添加零线参考
    ax2.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
    
    # 设置图表2样式
    ax2.set_title('连板股盘中最大跌幅', fontsize=16, pad=15)
    ax2.set_ylabel('最大跌幅(%)', fontsize=14)
    ax2.grid(axis='y', linestyle='--', alpha=0.7)
    ax2.legend(loc='upper left', bbox_to_anchor=(1, 1))
    ax2.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
    
    # 添加整体说明
    fig.text(0.5, 0.01, '数据来源: 交易所 | 最大跌幅=盘中最低点跌幅 | 红点=最高连板数', 
             ha='center', fontsize=10, alpha=0.7)
    
    # 调整布局
    plt.tight_layout(rect=[0, 0.03, 1, 0.97])
    plt.subplots_adjust(hspace=0.3)
    
    # 保存图片（适合手机查看）
    plt.savefig('mobile_streak_analysis.jpg', dpi=120, bbox_inches='tight')
    plt.show()
    
    return result

